1) Add the nav item (side panel)

src/routes.tsx

// imports near the others
+const Infographics = React.lazy(() => import("./pages/business-assets/templates/Infographics"));

export const NAV: NavItem[] = [
  {
    label: "Business Assets",
    icon: <Layers className="w-4 h-4" />,
    children: [
      { groupLabel: "Templates" },
      { label: "Cover & Divider Templates", path: "/business-assets/templates/cover-dividers", icon: <Presentation className="w-4 h-4" /> },
      { label: "Presentation Templates", path: "/business-assets/templates/presentation-template", icon: <Presentation className="w-4 h-4" /> },
+     { label: "Infographics", path: "/business-assets/templates/infographics", icon: <GitBranch className="w-4 h-4" /> },
      { label: "Business Plan Template", path: "/business-assets/templates/business-plan", icon: <GalleryVerticalEnd className="w-4 h-4" /> },

      { groupLabel: "Stock Library" },
      { label: "Icons", path: "/business-assets/stock/icons", icon: <Image className="w-4 h-4" /> },
      { label: "Mockups", path: "/business-assets/stock/mockups", icon: <Boxes className="w-4 h-4" /> },
      { label: "Stock Photos", path: "/business-assets/stock/photos", icon: <ImagePlus className="w-4 h-4" /> },
    ],
  },
];

export const ROUTES = [
  { path: "/business-assets/templates/cover-dividers", element: <CoverDividerTemplates /> },
  { path: "/business-assets/templates/presentation-template", element: <PresentationTemplate /> },
+ { path: "/business-assets/templates/infographics", element: <Infographics /> },
  // ...
];

2) Create the Infographics picker page

src/pages/business-assets/templates/Infographics.tsx

import React, { useMemo, useState } from "react";
import { useBrandKit } from "../../../utils/brand";
import { useLocalStorage } from "../../../hooks/useLocalStorage";
import { Upload } from "lucide-react";

type Size = "16x9" | "4x3";
type LayoutType = "hubSpoke" | "stepsHorizontal" | "mindmap";

type Item = {
  layout: LayoutType;
  count: number;
  centerTitle?: string;
  labels: string[];
  notes: string[];
  iconsSvg: (string | null)[];
};

const LAYOUTS: { id: LayoutType; name: string; blurb: string }[] = [
  { id: "hubSpoke", name: "Hub & Spoke (6)", blurb: "Center topic with nodes around" },
  { id: "stepsHorizontal", name: "Steps (6)", blurb: "Process steps in a row" },
  { id: "mindmap", name: "Mindmap (6)", blurb: "Branches from a central idea" },
];

export default function Infographics() {
  const brand = useBrandKit();
  const [size, setSize] = useLocalStorage<Size>("ig.size", "16x9");
  const [selected, setSelected] = useState<LayoutType>("hubSpoke");
  const [count, setCount] = useState<number>(6);
  const [centerTitle, setCenterTitle] = useState<string>("Topic");
  const [labels, setLabels] = useState<string[]>(Array.from({length: 6}, (_,i)=>`Point ${i+1}`));
  const [notes, setNotes] = useState<string[]>(Array.from({length: 6}, ()=>""));
  const [iconsSvg, setIconsSvg] = useState<(string|null)[]>(Array.from({length: 6}, ()=>null));

  // keep arrays sized with count
  useMemo(() => {
    setLabels((prev) => [...Array.from({length: count}, (_,i)=> prev[i] ?? `Point ${i+1}`)]);
    setNotes((prev)  => [...Array.from({length: count}, (_,i)=> prev[i] ?? "")]);
    setIconsSvg((prev)=> [...Array.from({length: count}, (_,i)=> prev[i] ?? null)]);
  }, [count]);

  const canDownload = count >= 2;

  function fileToDataUrl(f: File) {
    return new Promise<string>((resolve, reject) => {
      const r = new FileReader();
      r.onload = () => resolve(String(r.result));
      r.onerror = reject;
      r.readAsDataURL(f);
    });
  }

  async function handleIconPick(i: number, e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0];
    if (!f) return;
    const dataUrl = await fileToDataUrl(f);
    const next = iconsSvg.slice();
    next[i] = dataUrl;
    setIconsSvg(next);
  }

  async function downloadNow() {
    const payload = {
      size,
      layout: selected,
      count,
      centerTitle,
      labels,
      notes,
      colors: [], // cycle brand accents
      brand: { fonts: { heading: brand.fonts?.heading || "Inter", body: brand.fonts?.body || "Inter" }, accents: [
        brand.colors.primary || "#0EA5E9",
        brand.colors.accent  || "#F59E0B",
        brand.colors.neutral || "#64748B",
        "#8B5CF6", "#22C55E", "#EF4444"
      ]},
      iconsSvg,
      assets: { logoDataUrl: null, logoPlacement: "none", logoScale: 1 },
    };

    const res = await fetch("/api/infographics/generate", {
      method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload)
    });
    if (!res.ok) { alert("Generate failed"); return; }
    const blob = await res.blob();
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = "infographic.pptx"; a.click();
    URL.revokeObjectURL(url);
  }

  function addToTemplate() {
    const item: Item = { layout: selected, count, centerTitle, labels, notes, iconsSvg };
    const key = "pt.infographicsDetailed";
    const prev = JSON.parse(localStorage.getItem(key) || "[]");
    prev.push(item);
    localStorage.setItem(key, JSON.stringify(prev));
    alert("Added! These infographics will be included when you Generate the Presentation Template.");
  }

  return (
    <div className="p-6 space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold">Infographics</h1>
        <div className="flex gap-2">
          <button onClick={addToTemplate} className="px-4 py-2 rounded-lg border border-white/10 hover:bg-white/5">Add to Template</button>
          <button onClick={downloadNow} disabled={!canDownload}
            className={`px-4 py-2 rounded-lg ${canDownload ? "bg-emerald-600 hover:bg-emerald-700 text-white" : "bg-slate-700 text-slate-300 cursor-not-allowed"}`}>
            Download PPTX
          </button>
        </div>
      </div>

      <div className="grid xl:grid-cols-[340px,1fr] gap-6">
        {/* Left controls */}
        <aside className="rounded-xl border border-white/10 bg-slate-900/60 p-4 space-y-4">
          <div>
            <div className="text-sm text-slate-400">Size</div>
            <div className="mt-2 flex gap-2">
              {(["16x9","4x3"] as Size[]).map(s => (
                <button key={s} onClick={()=>setSize(s)}
                  className={`px-3 py-1.5 rounded-lg border text-sm ${size===s?"bg-white/10 border-white/20":"border-white/10 hover:bg-white/5"}`}>{s}</button>
              ))}
            </div>
          </div>

          <div>
            <div className="text-sm text-slate-400">Layout</div>
            <div className="mt-2 grid gap-2">
              {LAYOUTS.map(L => (
                <button key={L.id} onClick={()=>setSelected(L.id)}
                  className={`text-left rounded-lg border p-3 ${selected===L.id?"border-sky-500/50 bg-sky-500/5":"border-white/10 hover:bg-white/5"}`}>
                  <div className="font-medium">{L.name}</div>
                  <div className="text-xs text-slate-400">{L.blurb}</div>
                </button>
              ))}
            </div>
          </div>

          <div>
            <div className="text-sm text-slate-400">Count</div>
            <input type="range" min={2} max={12} step={1} value={count} onChange={(e)=>setCount(parseInt(e.target.value))}
              className="w-full mt-2" />
            <div className="text-xs text-slate-400">{count} items</div>
          </div>

          {selected !== "stepsHorizontal" && (
            <div>
              <div className="text-sm text-slate-400">Center Title</div>
              <input value={centerTitle} onChange={(e)=>setCenterTitle(e.target.value)}
                className="mt-1 w-full rounded-lg bg-slate-950/60 border border-white/10 p-2 text-slate-100" />
            </div>
          )}
        </aside>

        {/* Right editor */}
        <section className="rounded-xl border border-white/10 bg-slate-900/60 p-5">
          <div className="flex items-center justify-between mb-3">
            <h2 className="text-lg font-semibold">Labels & Icons</h2>
            <div className="text-xs text-slate-400">Click the icon slot to upload an SVG (optional).</div>
          </div>

          <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-3">
            {Array.from({length: count}).map((_, i) => (
              <div key={i} className="rounded-lg border border-white/10 p-3">
                <div className="text-xs text-slate-400 mb-1">#{(i+1).toString().padStart(2,"0")}</div>
                <input
                  value={labels[i] || ""}
                  onChange={(e)=>{ const n=[...labels]; n[i]=e.target.value; setLabels(n); }}
                  placeholder={`Label ${i+1}`}
                  className="w-full rounded bg-slate-950/60 border border-white/10 p-2 text-sm"
                />
                <input
                  value={notes[i] || ""}
                  onChange={(e)=>{ const n=[...notes]; n[i]=e.target.value; setNotes(n); }}
                  placeholder="Note (optional)"
                  className="w-full mt-2 rounded bg-slate-950/60 border border-white/10 p-2 text-sm"
                />
                <label className="mt-2 block">
                  <input type="file" accept="image/svg+xml" className="hidden" onChange={(e)=>handleIconPick(i, e)} />
                  <div className="mt-1 h-20 rounded bg-white/90 text-slate-700 flex items-center justify-center text-xs cursor-pointer border border-dashed border-slate-300">
                    {iconsSvg[i] ? "Replace SVG" : (<span className="inline-flex items-center gap-2"><Upload className="w-3 h-3" /> Upload SVG</span>)}
                  </div>
                </label>
              </div>
            ))}
          </div>
        </section>
      </div>
    </div>
  );
}

3) Make the Presentation Template consume these selections

Add a tiny status card + include the data in the build payload:

src/pages/business-assets/templates/PresentationTemplate.tsx (snippets)

// state
+ const [infographicsDetailed, setInfographicsDetailed] = useLocalStorage<any[]>("pt.infographicsDetailed", []);

// show a small card if there are items
+ {infographicsDetailed?.length > 0 && (
+   <div className="rounded-xl border border-sky-500/40 bg-sky-500/5 p-4 mb-4">
+     <div className="text-sm font-medium text-sky-300">{infographicsDetailed.length} infographic(s) queued</div>
+     <div className="mt-2 flex gap-2">
+       <button onClick={()=>{ setInfographicsDetailed([]); }} className="px-3 py-1.5 rounded-lg border border-white/10 hover:bg-white/5 text-xs">Clear</button>
+     </div>
+   </div>
+ )}

// buildConfig
function buildConfig() {
  const effectiveLogo = useBrandLogo ? (brand.logo?.dataUrl || null) : (logo || null);
  return {
    size,
    brand: { name: brand.name, fonts, accents },
    assets: { logoDataUrl: effectiveLogo, logoPlacement, logoScale },
    selections: { layouts, infographics: infoPages },
+   infographicsDetailed: infographicsDetailed || [],
    coverDividers: coverDividers || null,
  };
}

4) Update the build-template API to render detailed infographics

Add these helpers to your existing server/api/ppt/build-template.ts (inside the same file, reusing your palette/fonts):

function addInfographicDetailed(
  pptx: PPTXGenJS,
  s: PPTXGenJS.Slide,
  item: any,
  fonts: { heading: string; body: string },
  colors: string[],
  slideW: number,
  slideH: number
) {
  const col = (i: number) => rgb(colors[i % colors.length], "0EA5E9");
  const center = { x: slideW/2, y: slideH/2 };
  const count = Math.max(2, Math.min(item.count || 6, 12));

  if (item.layout === "hubSpoke") {
    const R = Math.min(slideW, slideH)*0.22;
    const node = Math.min(slideW, slideH)*0.11;
    s.addShape(pptx.ShapeType.ellipse, { x: center.x - node*0.7/2, y: center.y - node*0.7/2, w: node*0.7, h: node*0.7, fill: { color: "0F172A" }, line: { color: "FFFFFF", width: 1 } });
    s.addText(item.centerTitle || "Topic", { x: center.x - 2.5, y: center.y - 0.4, w: 5, h: 0.8, align: "center", fontFace: fonts.heading, fontSize: 22, bold: true, color: "FFFFFF" });

    for (let i=0;i<count;i++) {
      const a = (Math.PI*2 * i)/count - Math.PI/2;
      const nx = center.x + R*Math.cos(a), ny = center.y + R*Math.sin(a);

      s.addShape(pptx.ShapeType.line, { x: center.x, y: center.y, w: nx-center.x, h: ny-center.y, line: { color: "CBD5E1", width: 1.5 } });
      s.addShape(pptx.ShapeType.ellipse, { x: nx - node/2, y: ny - node/2, w: node, h: node, fill: { color: col(i) }, line: { color: "FFFFFF", width: 1 } });

      const lbl = (item.labels?.[i] ?? `Point ${i+1}`); const note = (item.notes?.[i] ?? "");
      s.addText([{ text: `${lbl}\n`, options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "1F2937" } }, ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "475569" } }] : [])], { x: nx - 1.6, y: ny + node/2 + 0.1, w: 3.2, h: 1.0, align: "center" });
    }
  }

  if (item.layout === "stepsHorizontal") {
    const marginX = 0.7, gap = 0.25, avail = slideW - marginX*2 - gap*(count-1);
    const boxW = avail / count, boxH = 1.4, y = slideH*0.42;
    for (let i=0;i<count;i++) {
      const x = marginX + i*(boxW + gap);
      s.addShape(pptx.ShapeType.roundRect, { x, y, w: boxW, h: boxH, rectRadius: 0.15, fill: { color: col(i) }, line: { color: "FFFFFF", width: 1 } });
      s.addText(String(i+1).padStart(2,"0"), { x: x+0.15, y: y+0.18, w: 0.6, h: 0.4, fontFace: fonts.heading, fontSize: 14, bold: true, color: "FFFFFF" });
      const lbl = (item.labels?.[i] ?? `Step ${i+1}`); const note = (item.notes?.[i] ?? "");
      s.addText([{ text: `${lbl}\n`, options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "FFFFFF" } }, ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "F8FAFC" } }] : [])], { x: x+0.85, y: y+0.2, w: boxW-1.05, h: boxH-0.4, align: "left" });
    }
    s.addShape(pptx.ShapeType.line, { x: marginX, y: y+boxH+0.15, w: slideW - marginX*2, h: 0, line: { color: "CBD5E1", width: 1 } });
  }

  if (item.layout === "mindmap") {
    const node = 1.3;
    s.addShape(pptx.ShapeType.ellipse, { x: center.x - node, y: center.y - node*0.7, w: node*2, h: node*1.4, fill: { color: "0F172A" }, line: { color: "FFFFFF", width: 1 } });
    s.addText(item.centerTitle || "Topic", { x: center.x - 2.5, y: center.y - 0.2, w: 5, h: 0.8, align: "center", fontFace: fonts.heading, fontSize: 22, bold: true, color: "FFFFFF" });

    const perSide = Math.ceil(count/2), leftX = 1.0, rightX = slideW - 4.0, topY = slideH*0.20, stepY = (slideH*0.60)/(perSide-1 || 1);
    let idx = 0;
    for (let j=0;j<perSide && idx<count;j++, idx++) {
      const y = topY + j*stepY;
      s.addShape(pptx.ShapeType.line, { x: center.x - 0.2, y: center.y, w: (leftX+2.2)-(center.x - 0.2), h: y - center.y, line: { color: "94A3B8", width: 1.25 } });
      s.addShape(pptx.ShapeType.roundRect, { x: leftX, y, w: 3.2, h: 1.0, rectRadius: 0.12, fill: { color: col(idx) }, line: { color: "FFFFFF", width: 1 } });
      const lbl = item.labels?.[idx] ?? `Point ${idx+1}`; const note = item.notes?.[idx] ?? "";
      s.addText([{ text: lbl + "\n", options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "FFFFFF" } }, ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "F8FAFC" } }] : [])], { x: leftX+0.25, y: y+0.12, w: 2.7, h: 0.8, align: "left" });
    }
    for (let j=0;j<perSide && idx<count;j++, idx++) {
      const y = topY + j*stepY;
      s.addShape(pptx.ShapeType.line, { x: center.x + 0.2, y: center.y, w: (rightX)-(center.x + 0.2), h: y - center.y, line: { color: "94A3B8", width: 1.25 } });
      s.addShape(pptx.ShapeType.roundRect, { x: rightX, y, w: 3.2, h: 1.0, rectRadius: 0.12, fill: { color: col(idx) }, line: { color: "FFFFFF", width: 1 } });
      const lbl = item.labels?.[idx] ?? `Point ${idx+1}`; const note = item.notes?.[idx] ?? "";
      s.addText([{ text: lbl + "\n", options: { bold: true, fontFace: fonts.heading, fontSize: 14, color: "FFFFFF" } }, ...(note ? [{ text: note, options: { fontFace: fonts.body, fontSize: 12, color: "F8FAFC" } }] : [])], { x: rightX+0.25, y: y+0.12, w: 2.7, h: 0.8, align: "left" });
    }
  }
}


Then in your main handler (after adding normal slides), render any detailed items:

// after body layouts/old infographics:
if (Array.isArray(b.infographicsDetailed) && b.infographicsDetailed.length) {
  const fonts = { heading: b.brand.fonts.heading, body: b.brand.fonts.body };
  const palette = b.brand.accents && b.brand.accents.length ? b.brand.accents : ["0EA5E9","F59E0B","10B981","8B5CF6","EF4444","64748B"];
  b.infographicsDetailed.forEach((item: any) => {
    const s = pptx.addSlide();
    addInfographicDetailed(pptx, s, item, fonts, palette, slideW, slideH);
    addLogoToSlide(s, b.assets, slideW, slideH);
  });
}

What you have now

New “Infographics” page under Business Assets → Templates with:

Layout picker (Hub & Spoke / Steps / Mindmap)

Count slider, labels/notes editor, optional SVG icon upload per item

Add to Template (saves to pt.infographicsDetailed)

Download PPTX (calls /api/infographics/generate)

Presentation Templates now shows queued infographics and includes them when you click Generate Template.